iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
Modern Web

前進React 生態系 : 技術應用與概念解析系列 第 3

Day 03 - 更複雜的泛型,那些好用的關鍵字和 Utility Types

  • 分享至 

  • xImage
  •  

在昨天只有介紹了 extends,還有其他好用的關鍵字。

typeof

typeof 關鍵字可以用來取得一個變數或表達式的型別。

const user = {
  name: "John",
  age: 30,
};

type UserType = typeof user; // { name: string; age: number; }

keyof

keyof 可以取得一個物件的所有 key,然後組成 Union Type。

type User = {
  name: string;
  age: number;
};

type UserKeys = keyof User; // 'name' | 'age'

in

in 是映射型別(Mapped Types)的語法,用來遍歷型別的所有屬性鍵,並根據這些鍵來創建或修改型別。

type Permissions = "read" | "write" | "delete";
type PermissionFlags = { [P in Permissions]: boolean };
// { read: boolean; write: boolean; delete: boolean }

Condition Type 和 infer

Condition Type

語法基本會長這樣

T extends U ? X : Y

TU 是型別,T extends U 是條件判斷。如果 TU 的子型別,則結果為 X。否則,結果為 Y

範例:

type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

條件型別也可以疊加使用,進行複雜的邏輯處理:

type Result<T> = T extends string
  ? { text: T }
  : T extends number
  ? { value: T }
  : never;

infer

infer 需要搭配 extends 和 Condition Type 使用。它可以在條件型別中推斷出某個特定的型別變數。

type ElementType<T> = T extends (infer U)[] ? U : T;

infer U 會嘗試推斷陣列中的元素型別,並將其存儲在變數 U 中。

另外 infer 也經常會搭配 never 來使用,算是一種 TypeScript 表達錯誤的方式或是表示某個型別不應該存在。

Utility Types

了解上面的概念後,就可以了解 Utility Types 是怎麼運作的了,都是以上概念組合而已。
Utility Types 有很多,這邊介紹比較常用到的。

Union Type 相關

  • Exclude 移除某個 Union Type 的值
type Exclude<T, U> = T extends U ? never : T;

type AllRoles = "admin" | "editor" | "viewer";
type ForbiddenRoles = "editor" | "viewer";
type AllowedRoles = Exclude<UserRoles, ForbiddenRoles>;
// "admin"
  • Extract 取出某個 Union Type 的值
type Extract<T, U> = T extends U ? T : never;

type AllRoles = "admin" | "editor" | "viewer";
type ActiveRoles = "admin" | "viewer";
type ActiveUserRoles = Extract<AllRoles, ActiveRoles>;
// "admin" | "viewer"

Object 相關

  • Record 可以透過給 property 和 type 的方式創造 object
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type UserPermissions = Record<"read" | "write" | "delete", boolean>;
  • Omit 可以移除 object 中某幾個 property 和 type
type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P] };
  • Pick 可以只取 object 中某幾個 property 和 type
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
  • Partial 把整個 object 的屬性改成可選的
type Partial<T> = { [P in keyof T]?: T[P] | undefined };

範例:

type UserProfile = {
  name: string;
  age: number;
  email: string;
};

type PublicUserProfile = Omit<UserProfile, "email">;
// { name: string; age: number; }

type ContactInfo = Pick<UserProfile, "email">;
// { email: string; }

type UpdateUserProfile = Partial<UserProfile>;
// { name?: string; age?: number; email?: string; }

Function 相關

  • ReturnType
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;
  • Parameters
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

範例:

function getUserInfo(userId: number): { name: string; age: number } {
  return {
    name: "John",
    age: 30,
  };
}

type UserInfo = ReturnType<typeof getUserInfo>;
// { name: string; age: number; }

type GetUserParams = Parameters<typeof getUserInfo>;
// [userId: number]

建立自己的 Utility Types

透過各種組合,也可以建立出自己的 Utility Types。
以下是一個範例,PickByValue 用來選擇型別中屬性值為指定型別的屬性:

type PickByValue<T, V> = Pick<
  T,
  { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
>;

type User = {
  name: string;
  age: number;
};

type StringProperties = PickByValue<User, string>; // { name: string; }

參考資料:
https://www.typescriptlang.org/docs/handbook/utility-types.html#intrinsic-string-manipulation-types
https://stackoverflow.com/questions/60067100/why-is-the-infer-keyword-needed-in-typescript
https://www.youtube.com/watch?v=hLZXJTm7TEk
https://chentsulin.medium.com/typescript-infer-%E7%9A%84%E5%BC%B7%E5%A4%A7%E5%8A%9F%E7%94%A8-9b43c4eac6fb


上一篇
Day 02 - 泛起來 ~ 讓泛型寫出更靈活的程式碼
下一篇
Day 04 - 掌握 TypeScript Discriminated Unions:提升 React 組件的靈活性與安全性
系列文
前進React 生態系 : 技術應用與概念解析22
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言